#include <fs.h>
#include "fat.h"

#include <mm/valloc.h>
#include <mm/slab.h>

#include <lib/math.h>

#include <device/block.h>

extern const inode_ops fat32_inode_ops;
extern const f_ops fat32_file_ops;

extern kmem_cache_t* fat_inode_kmem_cache;

volatile u32 ino = 0;

void fat_fill_inode(inode_t* inode, sdire_t* sdire) {
    fat_inode_t* finode = inode->data;
    finode->start = (sdire->cluster_h << 16 | sdire->cluster_l) & 0x0FFFFFFF;

    inode->fsize = sdire->file_length;
    
    if (sdire->flag & DIRECTORY) {
        inode->type = FS_DIR;
    } else {
        inode->type = FS_FILE;
    }
}

int fat32_file_write(inode_t* inode, void* buf, u32 len, u32 fpos) {
    return ENOSET;
}

int fat32_file_read(inode_t* inode, void* buf, u32 len, u32 fpos) {
    fat_inode_t* finode = inode->data;
    fat_sb_t* fsb = inode->sb->data;

    if (fpos >= inode->fsize) {
        return 0;
    }

    if (len + fpos > inode->fsize) {
        len = inode->fsize - fpos;
    }

    u32 nblk = ICEIL(len, fsb->cluster_size);

    void* cluster = valloc(fsb->cluster_size);
    u32 clus = finode->start;
    u32 jmp_clus = fpos / fsb->cluster_size;
    fat_nextN_clus(inode, clus, jmp_clus, &clus);
    void* ptr = buf;

    u32 offset = fpos % fsb->cluster_size;
    if (offset) {
        fat_get_cluster(inode, clus, cluster);
        memcpy(ptr, cluster + offset, fsb->cluster_size - offset);
        ptr += fsb->cluster_size - offset;
        len -= fsb->cluster_size - offset;
        fat_next_clus(inode, clus, &clus);
    }

    while (len > 0) {
        fat_get_cluster(inode, clus, cluster);
        u32 copy_size = MIN(len, fsb->cluster_size);
        memcpy(ptr, cluster, copy_size);
        len -= copy_size;
        ptr += copy_size;
        fat_next_clus(inode, clus, &clus);
        if (clus >= FAT32_CLUSTER_END) break;
    }
    return ptr - buf;
}

int fat32_file_close(file_t* file) {
    return 0;
}

int fat32_file_sync(file_t* file) {
    return 0;
}

int fat32_file_seek(inode_t* inode, size_t offset) {
    return 0;
}

int fat32_inode_open(inode_t* this, file_t* file) {
    return 0;
}

int fat32_inode_sync(inode_t* this) {
    return 0;
}

void fat32_init_inode(sb_t* sb, inode_t* inode) {
    inode->iops = &fat32_inode_ops;
    inode->fops = &fat32_file_ops;
    fat_inode_t* fat_inode = take_obj(fat_inode_kmem_cache);
    memset(fat_inode, 0, sizeof(fat_inode_t));
    inode->data = fat_inode;
    inode->id = ino++;
    fat_inode->vfs_inode = inode;
}

const inode_ops fat32_inode_ops = {
    .lookup = fat32_inode_lookup,
    .mkdir = fat32_inode_mkdir,
    .rmdir = fat32_inode_rmdir,
    .open = fat32_inode_open
};

const f_ops fat32_file_ops = {
    .read = fat32_file_read,
    .write = fat32_file_write,
    .close = fat32_file_close,
    .readdir = fat32_file_readdir
};